/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *   gbx-emu - gameboy.cpp                                                 *
 *   gbx-emu homepage: http://code.google.com/p/gbx-emu/                   *
 *   Copyright (C) 2009 olejl77@gmail.com                                  *
 *                                                                         *
 *   This program is free software: you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation, either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <QTime>
#include "gameboy.h"
#include "cpu.h"
#include "memory.h"
#include "screen.h"
#include "debugger.h"
#include "../gui/mainwindow.h"
#include "common.h"

Gameboy::Gameboy(QObject *parent) : QThread(parent)
{
    // Creating child classes
    _mem = new Memory(this);
    _cpu = new Cpu(this);
    _scr = new Screen(this);
    _dbg = new Debugger(this);
    _log = NULL;

    // Setting up "assosiations"
    _cpu->setMemory(_mem);
    _cpu->setScreen(_scr);
    _cpu->setDebugger(_dbg);
    _scr->setCpu(_cpu);
    _scr->setMemory(_mem);
    _mem->setCpu(_cpu);
    _dbg->setCpu(_cpu);
    _dbg->setMemory(_mem);

    // Initialize settings
    _frameLimiterEnabled = true;
}

Gameboy::~Gameboy()
{
    delete _mem;
    delete _cpu;
    delete _scr;
    delete _dbg;
    _mem = NULL;
    _cpu = NULL;
    _scr = NULL;
    _dbg = NULL;
    _ui = NULL;
    _log = NULL;
}

void Gameboy::init(MainWindow *ptr, Log *ptr_2)
{
    _ui = ptr;
    _log = ptr_2;
    _mem->setLog(_log);
    _cpu->setLog(_log);
    _scr->setLog(_log);
    _ui->setDebugger(_dbg);
    _ui->setCpu(_cpu);
    _dbg->setUi(_ui);
}

bool Gameboy::powerOn(QSize resultSize, QString fileName)
{
    bool ret = false;
    if (loadRom(fileName)) {
        this->_resultSize = resultSize;
        _image = new QImage(_resultSize, QImage::Format_Indexed8);
        _scr->setImage(_image);

        _dbg->dbgStart();

        // The Gamboy class inherits QThread which means that start() will do run()
        start();
        ret = true;
    }
    return ret;
}

void Gameboy::run()
{
    double fps;
    _quit = false;
    u32 frmCounter = 0;
    QTime time, frame;

    _cpu->resetCpu();
    time.start();
    frame.start();

    while (!_quit) {
        _cpu->Execute();
        emit updateScreen(*_image);
        if (++frmCounter == 60) {
            frmCounter = 0;
            fps = 60000 / time.elapsed();
            emit newFps(fps);
            time.restart();
        }

        if (_frameLimiterEnabled) {
            s32 tmp = 17 - frame.elapsed();
            if (tmp > 0) {
                this->msleep(tmp);
            }
            frame.restart();
        }
    }
    delete _image;
    _image = NULL;
    emit updateScreen(*_image);
}

bool Gameboy::loadRom(QString fileName)
{
    bool ret = true;

    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly)) {
        _log->Msg(ERROR_LEVEL, LOG_CART, "Failed to open rom file!");
        return false;
    }
    QDataStream in(&file);  // read the data serialized from the file
    _cartSize = (s32) file.size();
    _cart = new u8[_cartSize];
    if (_cart == NULL) {
        _log->Msg(ERROR_LEVEL, LOG_CART, "Dynamic memory allocation of cart failed!");
        file.close();
        return false;
    }
    if ((in.readRawData((char *)_cart, _cartSize)) != _cartSize) {
        _log->Msg(ERROR_LEVEL, LOG_CART, "Failed to read the cartridge from file!");
        file.close();
        return false;
    }
    file.close();
    memcpy(&_rom_header, _cart + HEADER_START, HEADER_SIZE);

    u8 x = 0;
    for (u32 i = 0x0134; i <= 0x014c; i++) {
        x = x - _cart[i] - 1;
    }
    if (x != _rom_header.complement_check) {
        _log->Msg(WARNING_LEVEL, LOG_CART, "Wrong Checksum. This ROM will not run in a real Gameboy!");
    }

    if (!_mem->Load(_cart, (ROM_TYPES)_rom_header.cart_type)) {
        ret = false;
    }

    return ret;
}

